package com.bstek.uflo.designer.view;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;

import com.bstek.dorado.annotation.DataProvider;
import com.bstek.dorado.annotation.Expose;
import com.bstek.dorado.data.provider.Page;
import com.bstek.dorado.view.View;
import com.bstek.dorado.view.manager.ViewConfig;
import com.bstek.dorado.view.manager.ViewConfigManager;
import com.bstek.dorado.view.widget.HtmlContainer;
import com.bstek.dorado.web.DoradoContext;
import com.bstek.uflo.console.controller.AssigneeInfo;
import com.bstek.uflo.designer.controller.FileKeyManager;
import com.bstek.uflo.designer.convert.ConvertService;
import com.bstek.uflo.designer.deploy.DeployService;
import com.bstek.uflo.designer.model.process.Process;
import com.bstek.uflo.designer.security.model.ProcessEntry;
import com.bstek.uflo.form.model.FormInfo;
import com.bstek.uflo.form.model.TableInfo;
import com.bstek.uflo.form.service.FormService;
import com.bstek.uflo.model.ProcessDefinition;
import com.bstek.uflo.process.assign.Assignee;
import com.bstek.uflo.process.assign.AssigneeProvider;
import com.bstek.uflo.process.assign.Entity;
import com.bstek.uflo.process.assign.PageQuery;
import com.bstek.uflo.process.handler.ActionHandler;
import com.bstek.uflo.process.handler.AssignmentHandler;
import com.bstek.uflo.process.handler.ConditionHandler;
import com.bstek.uflo.process.handler.CountersignHandler;
import com.bstek.uflo.process.handler.DecisionHandler;
import com.bstek.uflo.process.handler.ForeachHandler;
import com.bstek.uflo.process.handler.NodeEventHandler;
import com.bstek.uflo.process.handler.ProcessEventHandler;
import com.bstek.uflo.process.handler.ReminderHandler;
import com.bstek.uflo.process.node.FormTemplateProvider;
import com.bstek.uflo.process.node.reminder.CalendarInfo;
import com.bstek.uflo.process.node.reminder.CalendarProvider;
import com.bstek.uflo.query.ProcessQuery;
import com.bstek.uflo.service.ProcessService;
import com.bstek.uflo.utils.EnvironmentUtils;

/**
 * @author matt.yao@bstek.com
 * @since 1.0
 */
@Controller(UfloDesignerMaintain.BEAN_ID)
public class UfloDesignerMaintain {

	public static final String BEAN_ID = "ufloDesignerMaintain";

	@Autowired
	@Qualifier(ConvertService.BEAN_ID)
	private ConvertService convertService;

	@Autowired
	@Qualifier(DeployService.BEAN_ID)
	private DeployService deployService;

	@Autowired
	@Qualifier(ProcessService.BEAN_ID)
	private ProcessService processService;

	@Autowired
	@Qualifier(FormService.BEAN_ID)
	private FormService formService;
	
	@Autowired
	private ViewConfigManager viewConfigManager;

	public void onInitView(View view) {
		HtmlContainer htmlContainer = (HtmlContainer) view.getComponent("processNodeList");
		if (htmlContainer != null) {
			htmlContainer.setContent(buildProcessNodeHtml());
		}
		DoradoContext.getCurrent().setAttribute(DoradoContext.VIEW, "uflo_server_url", buildDeployUrl());
	}

	@Expose
	public String deployProcess(Map<String, Object> data) throws Exception {
		String graphData = (String) data.get("xmlData");
		String serverUrl = (String) data.get("serverUrl");
		Process process = convertService.graphToModel(graphData);
		File file = convertService.modelToUflo(process);
		FileInputStream fileInputStream = new FileInputStream(file);
		try {
			return deployService.remoteDeploy(fileInputStream, serverUrl);
		} finally {
			fileInputStream.close();
		}
	}

	@Expose
	public void updateProcess(Map<String, Object> data) throws Exception {
		String graphData = (String) data.get("xmlData");
		long processId = Long.valueOf(data.get("processId").toString());
		Process process = convertService.graphToModel(graphData);
		File file = convertService.modelToUflo(process);
		FileInputStream fileInputStream = new FileInputStream(file);
		try {
			deployService.nativeDeploy(fileInputStream, processId);
		} finally {
			fileInputStream.close();
		}

	}

	@Expose
	public String loadGraphData(String key) throws Exception {
		String fileKey = FileKeyManager.getFileKey(key);
		if (StringUtils.hasText(fileKey)) {
			File file = new File(fileKey);
			FileInputStream fileInputStream = new FileInputStream(file);
			try {
				String ufloXmlData = IOUtils.toString(fileInputStream, "UTF-8");
				Process process = convertService.ufloToModel(ufloXmlData);
				String graphXmlData = convertService.modelToGraph(process);
				return graphXmlData;
			} finally {
				fileInputStream.close();
			}

		}
		return null;
	}

	@Expose
	public String saveDesignData(Map<String, Object> data) throws Exception {
		String graphData = (String) data.get("xmlData");
		Process process = convertService.graphToModel(graphData);
		File file = convertService.modelToUflo(process);
		String fileId = UUID.randomUUID().toString();
		DoradoContext.getCurrent().getRequest().getSession().setAttribute(fileId, file.getAbsolutePath());
		return fileId;
	}

	private String buildProcessNodeHtml() {
		InputStream processNodeStream = this.getClass().getResourceAsStream("/dorado/resources/processNode.html");
		try {
			return IOUtils.toString(processNodeStream, "UTF-8");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				processNodeStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	private String buildDeployUrl() {
		HttpServletRequest request = DoradoContext.getCurrent().getRequest();
		String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/dorado/uflo/deploy.process";
		return url;
	}

	@DataProvider
	public void loadProcess(Page<ProcessDefinition> page) throws Exception {
		ProcessQuery query = processService.createProcessQuery();
		query.addOrderDesc("createDate");
		query.page((page.getPageNo() - 1) * page.getPageSize(), page.getPageSize());
		page.setEntityCount(query.count());
		page.setEntities(query.list());
	}

	@Expose
	public void deleteProcessDefinition(long processId) throws Exception {
		processService.deleteProcess(processId);
	}

	@Expose
	@Transactional
	public Map<String, Object> findProcessDefinitionXml(long processId) throws Exception {
		Map<String, Object> result = new HashMap<String, Object>();
		if (!StringUtils.hasText(EnvironmentUtils.getEnvironment().getLoginUser())) {
			result.put("nologin", true);
		}
		String ufloXmlData = convertService.findProcessDefinitionXml(processId);
		Process process = convertService.ufloToModel(ufloXmlData, processId);
		ProcessEntry processEntry = process.getProcessEntry();
		String graphXmlData = convertService.modelToGraph(process);
		result.put("data", graphXmlData);
		if (processEntry != null) {
			result.put("modifiable", processEntry.isModifiable());
			result.put("addable", processEntry.isAddable());
		}
		return result;
	}

	@Expose
	public Set<String> findHanderBean(String dataType, String property) {
		WebApplicationContext context = DoradoContext.getAttachedWebApplicationContext();
		if (property.equals("eventHandlerBean")) {
			if (dataType.equals("dataTypeProcess")) {
				return context.getBeansOfType(ProcessEventHandler.class).keySet();
			} else {
				return context.getBeansOfType(NodeEventHandler.class).keySet();
			}
		} else if (property.equals("handlerBean")) {
			if (dataType.equals("dataTypeDecision")) {
				return context.getBeansOfType(DecisionHandler.class).keySet();
			} else if (dataType.equals("dataTypeConnection")) {
				return context.getBeansOfType(ConditionHandler.class).keySet();
			} else if (dataType.equals("dataTypeAction")) {
				return context.getBeansOfType(ActionHandler.class).keySet();
			} else if (dataType.equals("dataTypeForeach")) {
				return context.getBeansOfType(ForeachHandler.class).keySet();
			}
		} else if (property.equals("formTemplate")) {
			Set<String> set = new HashSet<String>();
			for (FormTemplateProvider provider : context.getBeansOfType(FormTemplateProvider.class).values()) {
				set.add(provider.getFormTemplate());
			}
			return set;
		} else if (property.equals("reminderHandlerBean")) {
			return context.getBeansOfType(ReminderHandler.class).keySet();
		} else if (property.equals("assignmentHandlerBean")) {
			return context.getBeansOfType(AssignmentHandler.class).keySet();
		} else if (property.equals("countersignHandler")) {
			return context.getBeansOfType(CountersignHandler.class).keySet();
		}
		return null;
	}

	@DataProvider
	public List<CalendarInfo> findCalendarInfos() {
		List<CalendarInfo> result = new ArrayList<CalendarInfo>();
		Collection<CalendarProvider> calendarProviderList = DoradoContext.getAttachedWebApplicationContext().getBeansOfType(CalendarProvider.class).values();
		for (CalendarProvider calendarProvider : calendarProviderList) {
			List<CalendarInfo> calendarInfoList = calendarProvider.getCalendarInfos();
			if (calendarInfoList != null && calendarInfoList.size() > 0) {
				result.addAll(calendarInfoList);
			}
		}
		return result;
	}

	@DataProvider
	public List<AssigneeInfo> findAssigneeInfos() {
		Map<String, AssigneeProvider> assigneeProviderMap = DoradoContext.getAttachedWebApplicationContext().getBeansOfType(AssigneeProvider.class);
		List<AssigneeInfo> result = new ArrayList<AssigneeInfo>();
		for (Entry<String, AssigneeProvider> entry : assigneeProviderMap.entrySet()) {
			AssigneeProvider assigneeProvider = entry.getValue();
			if (!assigneeProvider.disable()) {
				AssigneeInfo info = new AssigneeInfo();
				info.setName(entry.getValue().getName());
				info.setTree(entry.getValue().isTree());
				info.setProviderId(entry.getKey());
				result.add(info);
			}
		}
		return result;
	}

	@DataProvider
	public void loadGridAssignees(Page<Assignee> page, String providerId, String parentId) throws Exception {
		PageQuery<Entity> pageQuery = new PageQuery<Entity>(page.getPageNo(), page.getPageSize());
		this.getAssigneeProvider(providerId).queryEntities(pageQuery, parentId);
		List<Assignee> assignees = this.buildEntitys(pageQuery.getResult(), providerId);
		page.setEntityCount(pageQuery.getRecordCount());
		page.setEntities(assignees);
	}

	@DataProvider
	public List<Assignee> laodTreeAssignees(String providerId, String parentId) throws Exception {
		PageQuery<Entity> pageQuery = new PageQuery<Entity>(1, 1000);
		this.getAssigneeProvider(providerId).queryEntities(pageQuery, parentId);
		List<Assignee> assignees = this.buildEntitys(pageQuery.getResult(), providerId);
		return assignees;
	}

	@DataProvider
	public List<TableInfo> loadTableInfo() {
		return formService.queryTableInfo();
	}

	@DataProvider
	public List<FormInfo> loadFormInfos(long tableId) {
		return formService.queryFormInfos(tableId);
	}

	private List<Assignee> buildEntitys(Collection<Entity> entitys, String providerId) {
		List<Assignee> assignees = new ArrayList<Assignee>();
		if (entitys != null) {
			for (Entity entity : entitys) {
				Assignee assignee = new Assignee();
				assignee.setId(entity.getId());
				assignee.setName(entity.getName());
				assignee.setProviderId(providerId);
				assignee.setChosen(entity.isChosen());
				assignees.add(assignee);
			}
		}
		return assignees;
	}

	private AssigneeProvider getAssigneeProvider(String providerId) {
		return DoradoContext.getAttachedWebApplicationContext().getBeansOfType(AssigneeProvider.class).get(providerId);
	}
	
	public ViewConfig getViewConfig(DoradoContext context, String viewName)
			throws Exception {
		ViewConfig viewConfig = (ViewConfig) context.getAttribute(viewName);
		if (viewConfig == null) {
			viewConfig = viewConfigManager.getViewConfig(viewName);
			context.setAttribute(viewName, viewConfig);
		}
		return viewConfig;
	}

}
